package algo_lignes_sites.data.segment;

import algo_lignes_sites.data.Data_ls;
import algo_lignes_sites.data.force.Force;
import algo_lignes_sites.data.force.ForceAccumulator;
import algo_lignes_sites.data.force.ForceAccumulator.TypeForce;
import algo_lignes_sites.data.junction.A_Junction;
import algo_lignes_sites.data.junction.JunctionDER;
import algo_lignes_sites.data.path.A_Path;
import algo_lignes_sites.data.path.PathDER;
import commons.params_appli.ParamsAppli;
import commons.utils.UtilsCalcul;
import commons.utils.UtilsGeom;
import commons.utils.primitive_geom.Point_d;
import commons.utils.primitive_geom.Rect_d;
import commons.utils.primitive_geom.Vector_d;
import java.util.ArrayList;
import java.util.Collections;


public class SegmentDER extends A_AlgolineSegment {

	public SegmentREF segREF;

	//***** Repulsion par les nodes, une force par extrémité du segment ********
	// Pour un meme segment, on somme les forces des nodes alentours
	//  en fin de traitement, quand les junctions récupèreront les forces de leurs divers segments, on moyenne
	private ForceAccumulator accuForceRepulsFromNode_1;
	private ForceAccumulator accuForceRepulsFromNode_2;

	private ForceAccumulator accuForceRepulsFromPath_1;
	private ForceAccumulator accuForceRepulsFromPath_2;
	
	private Force forceRespectForme_1;
	private Force forceRespectForme_2;
	
	public SegmentDER(A_Path path, A_Junction j1, A_Junction j2) {
		super(path, j1, j2);
		accuForceRepulsFromNode_1 = new ForceAccumulator(TypeForce.SUM);
		accuForceRepulsFromNode_2 = new ForceAccumulator(TypeForce.SUM);
		
		// Utilisé pour la repulsion seg->seg : on somme la contribution de chaque Path
		accuForceRepulsFromPath_1 = new ForceAccumulator(TypeForce.SUM);
		accuForceRepulsFromPath_2 = new ForceAccumulator(TypeForce.SUM);
		
		forceRespectForme_1 = new Force();
		forceRespectForme_2 = new Force();
	}
	
	public void resetForces() {
		accuForceRepulsFromNode_1.clear();
		accuForceRepulsFromNode_2.clear();
		accuForceRepulsFromPath_1.clear();
		accuForceRepulsFromPath_2.clear();
		forceRespectForme_1.clear();
		forceRespectForme_2.clear();
	}
	
	// Appelé par une des junctions extrémité du segment pour savoir quelle force s'applique à elle
	public Force getForceRepulsNodeForThisJunction(JunctionDER j) {
		if (pt1==j)
			return accuForceRepulsFromNode_1.getForce();
		else if (pt2==j)
			return accuForceRepulsFromNode_2.getForce();
		else {
			assert (false);
			return null;
		}
	}
	public Force getForceRepulsSegForThisJunction(JunctionDER j) {
		if (pt1==j)
			return accuForceRepulsFromPath_1.getForce();
		else if (pt2==j)
			return accuForceRepulsFromPath_2.getForce();
		else {
			assert (false);
			return null;
		}
	}
	public Force getForceRespectFormeForThisJunction(JunctionDER j) {
		if (pt1==j)
			return forceRespectForme_1;
		else if (pt2==j)
			return forceRespectForme_2;
		else {
			assert (false);
			return null;
		}
	}
	
	public void computeForcesRepuls(Data_ls data) {
		// Vecteurs des Path et des points proches du point en cours (qui ont une chance d'avoir une interaction avec
		double rayonInteraction;
		Rect_d rectInteraction;

		
		//***** Calcul repulsion seg -> seg
		rayonInteraction = this.path.epaisseurTrait_reel_m/2 + data.maxEpaisseurPath_reel_m/2 +
				           this.path.epaisseurDissymetrie_reel_m + data.maxEpaisseurDissymetrie_reel_m +
						   data.maxEspaceMiniAutourPath_reel_m;
		rectInteraction = new Rect_d(this);
		rectInteraction.expand(rayonInteraction, rayonInteraction);

		ArrayList<SegmentDER> vSegProche = data.getVSegmentDER(rectInteraction);
		// comme on va trier ce vecteur, on doit en faire une copie, car il peut s'agir d'un vecteur interne du SPI
		vSegProche = (ArrayList<SegmentDER>)vSegProche.clone();
		
		if (vSegProche.size()>0)
			computeRepuls_Seg_Seg (vSegProche);//, factAttenuationRepulsion);


		//***** Calcul repulsion node -> seg
		rayonInteraction = data.maxDiametreNode_reel_m + data.maxEspaceMiniAutourNode_reel_m;
		rectInteraction = new Rect_d(this);
		rectInteraction.expand(rayonInteraction, rayonInteraction);
		
		ArrayList<JunctionDER> vNodeProche = data.getVNodeDER(rectInteraction);
		if (vNodeProche.size()>0)
			computeRepuls_Node_Seg (vNodeProche);//, factAttenuationRepulsion, factAttenuationRepulsion_horsZoneDense);
	}

	
	// Repulsion seg -> seg
	private void computeRepuls_Seg_Seg(ArrayList<SegmentDER> vSegProche) {
		
		JunctionDER extr1 = (JunctionDER)pt1;
		JunctionDER extr2 = (JunctionDER)pt2;
		PathDER pathDER = (PathDER)this.path;
		
		Point_d[] tabPt = new Point_d[2];
		
		// On garde la force max de repulsion depuis un meme path
		//  puis on sommera les repulsion des différents path
		ForceAccumulator maxForceRepulsFromPath_1 = new ForceAccumulator(TypeForce.MAX);
		ForceAccumulator maxForceRepulsFromPath_2 = new ForceAccumulator(TypeForce.MAX);
		// Path courant
		A_Path currentPath = vSegProche.get(0).path;
		
		// On trie les segments par path (ce qui permet en fait de les regrouper par path)
		Collections.sort(vSegProche);
		
		// Pour chaque segment proche
		int sizeVSeg = vSegProche.size();
		for (int i=0; i<sizeVSeg; i++) {
	        SegmentDER seg = vSegProche.get(i);
			
			// Pas de répulsion par soi-même ...
			if (this==seg)
				continue;
			
			if (!seg.path.getEchelleGeneralization().bToBeGeneralized)
				continue;
			
			// Si meme path, pas de repulsion TODO ALGO a faire evoluer
			if (pathDER.superPath == seg.path.superPath)
				continue;
			
			// Test rapide pour voir si les 2 segments ne sont pas trivialement trop eloignés
			// En pratique, ca accelere peu ...
			double distMaxSearch = this.path.epaisseurTrait_reel_m/2 + seg.path.epaisseurTrait_reel_m/2 +
					               this.path.epaisseurDissymetrie_reel_m + seg.path.epaisseurDissymetrie_reel_m +
								   UtilsCalcul.max(this.path.espaceAutour_reel_m, seg.path.espaceAutour_reel_m);
			if (seg.xMin-this.xMax > distMaxSearch || this.xMin-seg.xMax > distMaxSearch || seg.yMin-this.yMax > distMaxSearch || this.yMin-seg.yMax > distMaxSearch)
				continue;
			
		
			// Calcul de la distance entre les 2 segments
			double dist = getDistance_Seg_Seg(this, seg, tabPt);
			// Si segments superposé complètement ou partiellement ou bien s'intersectant: on ne sait comment ils doivent se repousser ...
			if (dist<0) continue; 
			
			Point_d ptPlusProche_this = tabPt[0];
			Point_d ptPlusProche_seg  = tabPt[1];
			
			// Calcul de l'écartement mini entre les 2 segments
			//  et des points les plus proches correspondants

			double attenuationThis = UtilsCalcul.min( ((JunctionDER)pt1).getFactAttenuationRepulsion_complet(), ((JunctionDER)pt2).getFactAttenuationRepulsion_complet());
			double attenuationSeg  = UtilsCalcul.min( ((JunctionDER)seg.pt1).getFactAttenuationRepulsion_complet(), ((JunctionDER)seg.pt2).getFactAttenuationRepulsion_complet());
			
			//  1) ecartement standard due à l'épaisseur des traits et de l'espace entre ligne
			//double ecartStd = this.path.epaisseurTrait_reel_m/2 + seg.path.epaisseurTrait_reel_m/2 + Math.max(this.path.espaceAutour_reel_m, seg.path.espaceAutour_reel_m);
			double ecartStd = this.path.epaisseurTrait_reel_m/2 * attenuationThis  +
							   seg.path.epaisseurTrait_reel_m/2 * attenuationSeg   +
							UtilsCalcul.max(this.path.espaceAutour_reel_m * attenuationThis, seg.path.espaceAutour_reel_m * attenuationSeg);
		
			//  2) ecartement additionel du aux eventuelles dissymetries
			double ecartDissy=0;
			if (Vector_d.getSideForPoint(this, ptPlusProche_seg) == this.path.getContrainteGeom().sideDissymetrie)
				ecartDissy += this.path.epaisseurDissymetrie_reel_m * attenuationThis;
			if (Vector_d.getSideForPoint(seg, ptPlusProche_this) == seg.path.getContrainteGeom().sideDissymetrie)
				ecartDissy += seg.path.epaisseurDissymetrie_reel_m * attenuationSeg;
			
			// Limitation des dissymétries (les barbulles sont peu encombrantes visuellement, mais très pénalisantes en termes de forces de repulsion)
			//  A) Partie "violente" si dans l'influence d'un node, ou on reduit virtuellement l'épaisseur des dissymetries
			double maxInfluence;
			if (extr1.isNode() || extr2.isNode())
				maxInfluence = 1;
			else maxInfluence = UtilsCalcul.max(extr1.getMaxInfluence_node_ptMult() , extr2.getMaxInfluence_node_ptMult());
			ecartDissy *= (1-maxInfluence);
			
			double ecartMin = ecartStd + ecartDissy;
			
			
			double ecartReel = dist;
			if (ecartReel==0) {
				//Logger.log_WARN("DIST ligne-ligne = 0 : " + this.getMainPathContenant().getInfoBulle() + " / " + path.getInfoBulle());
				continue; // On passe au path suivant
			}

			if (ecartReel < ecartMin) {
				// this est trop proche de seg : on met une force de répulsion
					
				// Avant toute chose,
				// Si le path courant des segments parcourus a changé, on met à jour la force resultante
				// Et on redemarre sur une nouvelle force max pour le nouveau path courant
				if (seg.path != currentPath) {
					currentPath = seg.path;
					accuForceRepulsFromPath_1.add(maxForceRepulsFromPath_1.getForce());
					accuForceRepulsFromPath_2.add(maxForceRepulsFromPath_2.getForce());
					maxForceRepulsFromPath_1.clear();
					maxForceRepulsFromPath_2.clear();
				}
				
				double distPenetr = ecartMin - ecartReel;
				
				double xUnitDeplacement = (ptPlusProche_this.x - ptPlusProche_seg.x) / ecartReel;
				double yUnitDeplacement = (ptPlusProche_this.y - ptPlusProche_seg.y) / ecartReel;

				double dxForce, dyForce;
				dxForce = xUnitDeplacement * distPenetr;
				dyForce = yUnitDeplacement * distPenetr;

				//**** ATTENUATION DIVERSES ************************************
				// Réduction de l'écartement suivant l'angle entre les segments
				if (ParamsAppli.B_USE_COS) {
					double attenuationCos = 1;
					double cos = UtilsGeom.getAbsCosinusBetweenSeg(this, seg);
					attenuationCos = cos*(1-ParamsAppli.MIN_COS) + ParamsAppli.MIN_COS;
					dxForce *= attenuationCos;
					dyForce *= attenuationCos;
				}
				
				// Limitation des dissymétries (les barbulles sont peu encombrantes visuellement, mais très pénalisantes en termes de forces de repulsion)
				//  B) Partie plus subtile : on conserve la taille des dissymetries (hors zone d'influence des nodes)
				//   et donc le déclenchement de l'existance d'une force de répulsion
				//   mais on réduit la taille de cette force, ce qui contraint moins la junction, notamment en zones dense
				// Ecarter les lignes du fait de dissymetries est moins prioritaire
				//  que par l'epaisseur standard des lignes 
				if (ecartDissy>0) {
					double attenuationSiDissymetrie = ecartStd / (ecartStd+ecartDissy);
					dxForce *= attenuationSiDissymetrie;
					dyForce *= attenuationSiDissymetrie;
				}
				
				// Algo v23 11/03/2015 : une extrémité en commun -> atténuation de répulsion de moitié
				// diminue les écartements trop importants sur des lignes qui se tortillonent en triangle
				if (this.hasCommonExtremity(seg)) {
					dxForce *= 0.5;
					dyForce *= 0.5;
				}
					
				
				// On repartit la force sur les extrémités
				// l'extrémité la plus proche du point d'impact de la force récupère 100% de la force
				// l'autre extrémité récupère une partie de la force en fonction de sa distance au point d'impact
				double dist1 = extr1.distTo(ptPlusProche_this);
				double dist2 = extr2.distTo(ptPlusProche_this);
				double facteur_extr1;
				double facteur_extr2;
				if (dist1+dist2 == 0) {
					// segment vide, on affecte moitié moitié ...
					facteur_extr1 = 0.5;
					facteur_extr2 = 0.5;
					
				} else {
					facteur_extr1 = dist2/(dist1+dist2);
					facteur_extr2 = dist1/(dist1+dist2);
					if (facteur_extr1>0.5) facteur_extr1=1;
					if (facteur_extr2>0.5) facteur_extr2=1;
				}
//				facteur_extr1 = 1;
//				facteur_extr2 = 1;
				
				// On n'applique pas une force sur un node (une fonction spécifique gère la répulsion des nodes)
				// Une extrémité type connect3 ou intersect ne doit pas etre repoussée par un path qui lui est connecté directement
				if (extr1.isNode() || extr1.isIntersectOrConnect3() && extr1.faitPartieDuMemePathOuSuperPath(seg.path))
					;
				// Cas normal
				else {
//					accuForceRepulsFromSeg_1.add(xForce * facteur_extr1, yForce * facteur_extr1);
					maxForceRepulsFromPath_1.add(dxForce * facteur_extr1, dyForce * facteur_extr1);
				}
				
				if (extr2.isNode() || extr2.isIntersectOrConnect3() && extr2.faitPartieDuMemePathOuSuperPath(seg.path))
					;
				else {
//					accuForceRepulsFromSeg_2.add(xForce * facteur_extr2, yForce * facteur_extr2);
					maxForceRepulsFromPath_2.add(dxForce * facteur_extr2, dyForce * facteur_extr2);
				}

			}
		}

		// Dernière mise à jour de la resultante
		accuForceRepulsFromPath_1.add(maxForceRepulsFromPath_1.getForce());
		accuForceRepulsFromPath_2.add(maxForceRepulsFromPath_2.getForce());
	}
	
	private double getDistance_Seg_Seg(SegmentDER seg1, SegmentDER seg2, Point_d[] tabPt) {
		
		// Si les segments sont égaux ou s'intersectent, on renvoie -1 et tabPt n'est pas rempli
		if (UtilsGeom.areSegmentSuperposed(seg1, seg2))
			return -1;
		if (UtilsGeom.areSegmentInternallyIntersected(seg1, seg2))
			return -1;
	
		// Cas normal de 2 segments séparés
		return UtilsGeom.getClosestPt_seg_seg(seg1, seg2, tabPt, true);
	}
	
	
	// Repulsion node -> seg
	private void computeRepuls_Node_Seg(ArrayList<JunctionDER> vNodeProche) {//, double factAttenuationRepulsion, double factAttenuationRepulsion_horsZoneDense) {
		
		JunctionDER extr1 = (JunctionDER)pt1;
		JunctionDER extr2 = (JunctionDER)pt2;
		PathDER pathDER = (PathDER)this.path;
		
		// Pour chaque node proche
		int sizeVNode = vNodeProche.size();
		for (int i=0; i<sizeVNode; i++) {
	        JunctionDER node = vNodeProche.get(i);

			// On ne repousse pas un segment si son path est connecté au node et dans l'influence de celui-ci
//			if (this.path.isConnectedToNode(node)) continue;
//			if (extr1.getFactorInfluencement_NODE(node)==0 || extr2.getFactorInfluencement_NODE(node)==0)
//				continue;
			// NON, on choisit de repousser un path, meme par un node auquel il est connecté directement ou indirectement
			//  par contre, on atténuera cette repulsion en fonction de cette influence, cf. un peu plus loin dans le code

			double ecartMin = 0;
			// Epaisseur standard
			ecartMin += pathDER.epaisseurTrait_reel_m/2 + node.diametre_reel_m/2 + UtilsCalcul.max(pathDER.espaceAutour_reel_m, node.espaceAutour_reel_m);
			// Si le path a une dissymétrie du mauvais coté
			if (pathDER.hasDissymetrie() &&	Vector_d.getSideForPoint(this, node) == pathDER.getContrainteGeom().sideDissymetrie)
				ecartMin += pathDER.epaisseurDissymetrie_reel_m;

			Point_d ptPlusProche = UtilsGeom.getClosestPt(this, node);
			double ecartReel = ptPlusProche.distTo(node);
			
	        if (ecartReel==0)
				//Logger.log_WARN("!! DIST Segment-Node = 0 !!");
				continue;

			else if (ecartReel<ecartMin) { // il n'y a de force que si cette condition est respectée
	            double distPenetr = ecartMin-ecartReel;

	            // vecteur unitaire pt -> ptPlusProche
	            double xUnitDeplacement = (ptPlusProche.x - node.x) / ecartReel;
	            double yUnitDeplacement = (ptPlusProche.y - node.y) / ecartReel;

				double dxForce, dyForce;
				dxForce = xUnitDeplacement * distPenetr;
	            dyForce = yUnitDeplacement * distPenetr;

				// On repartit la force sur les extrémités
				// l'extrémité la plus proche du point d'ipact de la force récupère 100% de la force
				// l'autre extrémité récupère une partie de la force en fonction de sa distance au point d'impact
				double dist1 = extr1.distTo(ptPlusProche);
				double dist2 = extr2.distTo(ptPlusProche);
				double facteur_extr1;
				double facteur_extr2;
				if (dist1+dist2 == 0) {
					// segment vide, on affecte moitié moitié ...
					facteur_extr1 = 0.5;
					facteur_extr2 = 0.5;
					
				} else {
					facteur_extr1 = dist2/(dist1+dist2);
					facteur_extr2 = dist1/(dist1+dist2);
					if (facteur_extr1>0.5) facteur_extr1=1;
					if (facteur_extr2>0.5) facteur_extr2=1;
				}
				
				// Si une des extrémité est influencée par le node en cours, on atténue sa répuslsion d'autant
				//  but, si proche node, il ne se passe rien
				//   si suffisament éloigné (genre un path qui revient près de son node), alors il y a répulsion normale
// Ce qui suit ne va pas : si node simple, on peut avoir influence forte et attenuation tres faible, toutes les junctions vont etre litteralement
//  expulsées des nodes simple, entrainant des
// On prend donc simplement le 1-facteur d'influencement
//				facteur_extr1 *= JunctionDER.getAttenuationRepulsionForNode(node, extr1.getFactorInfluencement_NODE(node));
//				facteur_extr2 *= JunctionDER.getAttenuationRepulsionForNode(node, extr2.getFactorInfluencement_NODE(node));
				facteur_extr1 *= 1 - extr1.getFactorInfluencement_NODE(node);
				facteur_extr2 *= 1 - extr2.getFactorInfluencement_NODE(node);
				
				// On n'applique pas une force sur un node (une fonction spécifique gère la répulsion des nodes)
				if (!extr1.isNode())
					accuForceRepulsFromNode_1.add(dxForce * facteur_extr1, dyForce * facteur_extr1);
				if (!extr2.isNode())
					accuForceRepulsFromNode_2.add(dxForce * facteur_extr2, dyForce * facteur_extr2);
			}
		}
	}
	
	
	
	
	public void computeForceRespectForme() {
		JunctionDER extr1 = (JunctionDER)pt1;
		JunctionDER extr2 = (JunctionDER)pt2;
		
		if (Double.isNaN(this.angle) || Double.isNaN(segREF.angle))
			// L'un des segments est vide, pas de force à calculer
			return;

		double lgSeg = pt1.distTo(pt2);

		double diff_angle = this.angle - segREF.angle;

		// si la difference d'angle depasse Pi, on demande à tourner dans l'autre sens
		if (diff_angle>Math.PI)
			diff_angle = - (2*Math.PI - diff_angle);
		// idem a l'oppose
		if (diff_angle<-Math.PI)
			diff_angle = 2*Math.PI + diff_angle;

		// mnt diff_angle est compris entre -Pi et +Pi
		double amplitudeForce = diff_angle / Math.PI * lgSeg;

		if (((A_Junction)pt1).isIntersect() || ((A_Junction)pt2).isIntersect())
			amplitudeForce *= 5;

		// on applique une force opposée aux 2 extrémités du segment
		double ux_trigo = - (pt2.y-pt1.y) / lgSeg;
		double uy_trigo = + (pt2.x-pt1.x) / lgSeg;

		// On n'applique pas une force sur un node (une fonction spécifique gère la répulsion des nodes)
		if (!extr1.isNode())
			forceRespectForme_1.setForce(+ux_trigo*amplitudeForce , +uy_trigo*amplitudeForce);
		if (!extr2.isNode())
			forceRespectForme_2.setForce(-ux_trigo*amplitudeForce , -uy_trigo*amplitudeForce);

/*
 //TEST autre methode, mais pas specialement convaincant
		JunctionDER j1 = (JunctionDER)pt1;
		JunctionDER j2 = (JunctionDER)pt2;
//		double dx1 = (j1.jREF.x-j1.x) - (j2.jREF.x-j2.x);
//		double dy1 = (j1.jREF.y-j1.y) - (j2.jREF.y-j2.y);
//
//		if (j1.isIntersect() || j2.isIntersect()) {
//			dx1 *= 5;
//			dy1 *= 5;
//		}
//
//
//		double dx2 = -dx1;
//		double dy2 = -dy1;


		double xCenterREF = (j1.jREF.x + j2.jREF.x) / 2;
		double yCenterREF = (j1.jREF.y + j2.jREF.y) / 2;

		double xCenterDER = (j1.x + j2.x) / 2;
		double yCenterDER = (j1.y + j2.y) / 2;

		double dx1 = (j1.jREF.x - (j1.x + (xCenterREF - xCenterDER)));
		double dy1 = (j1.jREF.y - (j1.y + (yCenterREF - yCenterDER)));

		if (j1.isIntersect() || j2.isIntersect()) {
			dx1 *= 5;
			dy1 *= 5;
		}

		double dx2 = -dx1;
		double dy2 = -dy1;

		j1.accuForceRespectForme( dx1 , dy1 );
		j2.accuForceRespectForme( dx2 , dy2 );

 */
	}





}
